package com.wwj.my_ummary;

import com.alibaba.fastjson.JSON;
import org.junit.Assert;
import org.junit.Test;

import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * 参考：https://blog.csdn.net/paincupid/article/details/82054647
 * @author crossoverJie
 *         Date: 03/01/2018 16:52
 * @since JDK 1.8
 */
public class RedPacketTools {

    public static BigDecimal MONEY_1_100 = new BigDecimal("0.01");

    public static final BigDecimal TIMES_100 = new BigDecimal(100);

    public static BigDecimal MONEY_200 = new BigDecimal(10);

    private static final SecureRandom random = new SecureRandom();

    /**
     * 红包随机算法（分和元转换）
     * @param money 红包总金额，单位为元
     * @param count 红包份数
     * @return 返回值的金额单位为元
     */
    public static List<BigDecimal> createBonusList(BigDecimal money, int count) {

        List<BigDecimal> moneys = new ArrayList<>(count);

        int _money = money.multiply(TIMES_100).intValue();
        int rdMin = MONEY_1_100.multiply(TIMES_100).intValue();
        int rdMax = MONEY_200.multiply(TIMES_100).intValue();

        int[] _moneys = createBonusList(_money, count, rdMin, rdMax, 0.8);
        for (int m: _moneys) {
            moneys.add(new BigDecimal(m).divide(TIMES_100));
        }

        return moneys;
    }

    /**
     * 返回一次抽奖在指定中奖概率下是否中奖
     * @param rate 中奖概率
     * @return xxx
     */
    private static boolean canReward(double rate) {
        return Math.random() <= rate;
    }

    /**
     * 返回min~max区间内随机数，含min和max
     * @param min xxx
     * @param max xxx
     * @return xxx
     */
    private static int getRandomVal(int min, int max) {
        return random.nextInt(max - min + 1) + min;
    }

    /**
     * 带概率偏向的随机算法，概率偏向subMin~subMax区间
     * 返回boundMin~boundMax区间内随机数（含boundMin和boundMax），同时可以指定子区间subMin~subMax的优先概率
     * 例：传入参数(10, 50, 20, 30, 0.8)，则随机结果有80%概率从20~30中随机返回，有20%概率从10~50中随机返回
     * @param boundMin 边界
     * @param boundMax xxx
     * @param subMin xxx
     * @param subMax xxx
     * @param subRate xxx
     * @return xxx
     */
    private static int getRandomValWithSpecifySubRate(int boundMin, int boundMax, int subMin, int subMax, double subRate) {
        if (canReward(subRate)) {
            return getRandomVal(subMin, subMax);
        }
        return getRandomVal(boundMin, boundMax);
    }

    /**
     * 随机分配第n个红包
     * @param totalBonus 总红包量
     * @param totalNum 总份数
     * @param sendedBonus 已发送红包量
     * @param sendedNum 已发送份数
     * @param rdMin 随机下限
     * @param rdMax 随机上限
     * @return xxx
     */
    private static int randomBonusWithSpecifyBound(int totalBonus, int totalNum, int sendedBonus,
                                                   int sendedNum, int rdMin, int rdMax, double bigRate) {
        double avg = totalBonus / (double)totalNum;  // 平均值
        int leftLen = (int) Math.ceil(avg - rdMin);
        int rightLen = (int) Math.floor(rdMax - avg);
        int boundMin;
        int boundMax;

        // 大范围设置小概率
        int i = totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMax;
        int a = totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMin;
        if (leftLen == rightLen) {
            boundMin = Math.max(i, rdMin);
            boundMax = Math.min(a, rdMax);
        } else if (rightLen- leftLen > 0) {
            // 上限偏离
            int standardRdMax = (int) Math.ceil(avg + leftLen);  // 右侧对称上限点
            int _rdMax = canReward(bigRate) ? rdMax : standardRdMax;
            boundMin = Math.max((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * standardRdMax), rdMin);
            boundMax = Math.min(a, _rdMax);
        } else {
            // 下限偏离
            int standardRdMin = (int) Math.floor(avg - rightLen);  // 左侧对称下限点
            int _rdMin = canReward(bigRate) ? rdMin : standardRdMin;
            boundMin = Math.max(i, _rdMin);
            boundMax = Math.min((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * standardRdMin), rdMax);
        }

        // 已发平均值偏移修正-动态比例
        if (boundMin == boundMax) {
            return getRandomVal(boundMin, boundMax);
        }
        double currAvg = sendedNum == 0 ? avg : (sendedBonus / (double)sendedNum);  // 当前已发平均值
        double middle = (boundMin + boundMax) / 2.0;
        int subMin = boundMin, subMax = boundMax;
        // 期望值
        double exp = avg - (currAvg - avg) * sendedNum / (double)(totalNum - sendedNum);
        if (middle > exp) {
            subMax = (int) Math.round((boundMin + exp) / 2.0);
        } else {
            subMin = (int) Math.round((exp + boundMax) / 2.0);
        }
        int expBound = (boundMin + boundMax) / 2;
        int expSub = (subMin + subMax) / 2;
        double subRate = (exp - expBound) / (double)(expSub - expBound);
        return getRandomValWithSpecifySubRate(boundMin, boundMax, subMin, subMax, subRate);
    }


    /**
     * 生成红包一次分配结果
     * @param totalBonus xxx
     * @param totalNum xxx
     * @param rdMin xxx
     * @param rdMax xxx
     * @param bigRate 指定大范围区间的概率
     * @return xxx
     */
    private static int[] createBonusList(int totalBonus, int totalNum, int rdMin, int rdMax, double bigRate) {
        int sendedBonus = 0;
        int sendedNum = 0;
        int[] bonusList = new int[totalNum];
        while (sendedNum < totalNum) {
            int bonus = randomBonusWithSpecifyBound(totalBonus, totalNum, sendedBonus, sendedNum, rdMin, rdMax, bigRate);
            bonusList[sendedNum] = bonus;
            sendedNum++;
            sendedBonus += bonus;
        }
        return bonusList;
    }



    @Test
    public void testCommon() {

        BigDecimal totalMoney = new BigDecimal("0.03");
        int totalAmount = 2;
        int loopTimes = 100;

        long start = System.currentTimeMillis();

        for (int k = 0; k < loopTimes; k++) {
            List<BigDecimal> bonusList = createBonusList(totalMoney, totalAmount);
            System.out.println(JSON.toJSONString(bonusList));

            BigDecimal sum = new BigDecimal(0);
            for (BigDecimal m : bonusList) {
                sum = sum.add(m);
            }
            Assert.assertEquals(totalMoney.compareTo(sum), 0);
//            System.out.println("sum：" + sum + "，人数：" + totalAmount);
//            System.out.println("-----------------");
        }

        long end = System.currentTimeMillis();
        System.out.println("金额：" + totalMoney + "\t人数：" + totalAmount + "\t时间间隔:" + (end - start) + "毫秒");

    }


    @Test
    public void testAllInteger() {

//        int moneyMax = 1;
//        int moneyMax = 200; // 慎用测试
//        int perMoneyAmountLoopTimes = 100000;

        int moneyMax = 10;
        int perMoneyAmountLoopTimes = 10;

        // 金额范围1 ~ 200
        for (int i = 1; i <= moneyMax; i++) {

            for (int amount = 1; amount <= i; amount++) {

//                long start = System.currentTimeMillis();

                // 金额：money，份数：amount的红包，循环perMoneyAmountLoopTimes次随机测试
                for (int k = 0; k < perMoneyAmountLoopTimes; k++) {
                    int[] bonusList = createBonusList(i, amount, 1, 200, 0.8);
                    System.out.println(Arrays.toString(bonusList));

                    int sum = 0;
                    for (int m : bonusList) {
                        sum += m;
                    }
                    Assert.assertEquals(sum, i);
//                    System.out.println("sum：" + sum + "，人数：" + amount);
//                    System.out.println("-----------------");
                }

//                long end = System.currentTimeMillis();
//                System.out.println("金额：" + money + "\t人数：" + amount + "\t时间间隔:" + (end - start) + "毫秒");
//                System.out.println("----------------------------------------------------");

            }
            System.out.println("金额：" + i + "(分)");
        }
    }


    @Test
    public void testAllFloat() {

        // 金额范围0.01 ~ 200
//        int moneyMax = 1;
//        int moneyMax = 20000; // 慎用测试
//        int perMoneyAmountLoopTimes = 100000;

        int moneyMax = 10;
        int perMoneyAmountLoopTimes = 10;

        for (int i = 1; i <= moneyMax; i++) {

            BigDecimal money = new BigDecimal(i).divide(TIMES_100);
            int amountMax = Math.min(i, 100);

            for (int amount = 1; amount <= amountMax; amount++) {

//                long start = System.currentTimeMillis();


                // 金额：money，份数：amount的红包，循环perMoneyAmountLoopTimes次随机测试
                for (int k = 0; k < perMoneyAmountLoopTimes; k++) {
                    List<BigDecimal> bonusList = createBonusList(money, amount);
                    System.out.println(JSON.toJSONString(bonusList));

                    BigDecimal sum = new BigDecimal(0);
                    for (BigDecimal m : bonusList) {
                        sum = sum.add(m);
                    }
                    Assert.assertEquals(money.compareTo(sum), 0);
//                    System.out.println("sum：" + sum + "，人数：" + amount);
//                    System.out.println("-----------------");
                }

//                long end = System.currentTimeMillis();
//                System.out.println("金额：" + money + "\t人数：" + amount + "\t时间间隔:" + (end - start) + "毫秒");
//                System.out.println("----------------------------------------------------");
            }
            System.out.println("金额：" + i + "(分)");
        }
    }

}